/*
 * Copyright (c) 2006-2023, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2019-05-08     chenyong     first version
 */

#include <stdlib.h>
#include <string.h>

#include <at_device.h>

#define DBG_TAG              "at.dev"
#define DBG_LVL              DBG_INFO
#include <rtdbg.h>

/* The global list of at device */
static rt_slist_t at_device_list = RT_SLIST_OBJECT_INIT(at_device_list);
static RT_DEFINE_SPINLOCK(_spinlock);

/**
 * This function will get the first initialized AT device.
 *
 * @return the AT device structure pointer
 */
struct at_device *at_device_get_first_initialized(void)
{
    rt_base_t level;
    rt_slist_t *node = RT_NULL;
    struct at_device *device = RT_NULL;

    level = rt_spin_lock_irqsave(&_spinlock);

    rt_slist_for_each(node, &at_device_list)
    {
        device = rt_slist_entry(node, struct at_device, list);
        if (device)
        {
           rt_spin_unlock_irqrestore(&_spinlock, level);
           return device;
        }
    }

    rt_spin_unlock_irqrestore(&_spinlock, level);

    return RT_NULL;
}

#ifdef AT_USING_SOCKET
/**
 * This function will get AT device by ip address.
 *
 * @param ip_addr input ip address
 * network
 * @return != NULL: network interface device object
 *            NULL: get failed
 */
struct at_device *at_device_get_by_ipaddr(ip_addr_t *ip_addr)
{
    rt_base_t level;
    rt_slist_t *node = RT_NULL;
    struct at_device *device = RT_NULL;

    level = rt_spin_lock_irqsave(&_spinlock);

    rt_slist_for_each(node, &at_device_list)
    {
        device = rt_slist_entry(node, struct at_device, list);
        if (device && ip_addr_cmp(ip_addr, &(device->netdev->ip_addr)))
        {
           rt_spin_unlock_irqrestore(&_spinlock, level);
           return device;
        }
    }

    rt_spin_unlock_irqrestore(&_spinlock, level);

    return RT_NULL;

}
#endif /* AT_USING_SOCKET */


/**
 * This function will perform a variety of control functions on AT devices.
 *
 * @param device the pointer of AT device structure
 * @param cmd the command sent to AT device
 * @param arg the argument of command
 *
 * @return = 0: perform successfully
 *         < 0: perform failed
 */
int at_device_control(struct at_device *device, int cmd, void *arg)
{
    char *dev_name = device->client.device->parent.name;

    if (device->device_ops->control)
    {
        return device->device_ops->control(device, cmd, arg);
    }
    else
    {
        LOG_W("AT device(%s) not support control operations.", dev_name);
        return RT_EOK;
    }
}

int at_device_netdev_add(struct at_device *device, const char *dev_name, struct netdev_ops *ops)
{
#define ETHERNET_MTU        1500
#define HWADDR_LEN          6
    struct netdev *netdev = RT_NULL;

    if (device->netdev)
    {
        return -1;
    }
    netdev = netdev_get_by_name(dev_name);
    if (netdev != RT_NULL)
    {
        return -1;
    }
    netdev = (struct netdev *) rt_calloc(1, sizeof(struct netdev));
    if (netdev == RT_NULL)
    {
        return -1;
    }
    device->netdev = netdev;
    netdev->mtu = ETHERNET_MTU;
    netdev->ops = ops;
    netdev->hwaddr_len = HWADDR_LEN;

#ifdef SAL_USING_AT
    extern int sal_at_netdev_set_pf_info(struct netdev *netdev);
    /* set the network interface socket/netdb operations */
    sal_at_netdev_set_pf_info(netdev);
#endif

    netdev_register(netdev, dev_name, device);

    return 0;
}

/**
 * This function registers an AT device with specified device name and AT client name.
 *
 * @param device the pointer of AT device structure
 * @param device_name AT device name
 * @param at_client_name AT device client name
 * @param class_id AT device class ID
 * @param user_data user-specific data
 *
 * @return = 0: register successfully
 *         < 0: register failed
 */
int at_device_register(struct at_device *device)
{
    rt_base_t level;

    RT_ASSERT(device);
    RT_ASSERT(device->device_ops);

    /* Fill AT device object*/
#ifdef AT_USING_SOCKET
    RT_ASSERT(device->socket_ops);

    device->sockets = (struct at_socket *) rt_calloc(device->socket_num, sizeof(struct at_socket));
    if (device->sockets == RT_NULL)
    {
        LOG_E("no memory for AT Socket number(%d) create.", device->socket_num);
        return -RT_ENOMEM;
    }
#endif /* AT_USING_SOCKET */

    /* Initialize current AT device single list */
    rt_slist_init(&(device->list));

    level = rt_spin_lock_irqsave(&_spinlock);

    /* Add current AT device to device list */
    rt_slist_append(&at_device_list, &(device->list));

    rt_spin_unlock_irqrestore(&_spinlock, level);

    /* Initialize AT device */
    return device->device_ops->init(device);
}
